/*
 * To change this license header, choose License Headers in Project Properties.
 * To change this template file, choose Tools | Templates
 * and open the template in the editor.
 */

package pt.mleiria.skip;

import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.ServerSocket;
import java.net.Socket;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.KeyFactory;
import java.security.KeyPair;
import java.security.NoSuchAlgorithmException;
import java.security.PublicKey;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.X509EncodedKeySpec;
import java.text.MessageFormat;
import javax.crypto.KeyAgreement;
import org.apache.commons.codec.binary.Base64;
import org.apache.log4j.Logger;
import pt.mleiria.algorithm.KeyExchange;
import pt.mleiria.generator.KeyPairGen;

/**
 * So faz key-agreement com duas partes
 * @author NB13712
 */
public class SkipServer {
    private static final Logger LOG = Logger.getLogger(SkipServer.class);
    public static final int port = 69;
    private static KeyPair keyPair;
    private static byte[] secret;

    public static void start() throws NoSuchAlgorithmException, InvalidAlgorithmParameterException, IOException, InvalidKeySpecException, InvalidKeyException {
        //Create a Diffie-Hellman Key pair
        final KeyPairGen kpg = new KeyPairGen(KeyExchange.DH.value(), Skip.sDHParameterSpec);
        keyPair = kpg.getKeyPair();
        //wait for a connection
        final ServerSocket ss = new ServerSocket(port);
        LOG.info(MessageFormat.format("Listening on port:{0}", port));
        final Socket s = ss.accept();
        final DataOutputStream out = new DataOutputStream(s.getOutputStream());
        final DataInputStream in = new DataInputStream(s.getInputStream());
        final PublicKey theirPublicKey = receivePublicKey(in);
        LOG.info("Public key sent to client.");
        sendPublicKey(out);
        generateSecretValue(theirPublicKey);
        LOG.info("Public key received from client.");
        //clean up
        out.close();
        in.close();
    }
    /**
     * receive a public key from the client
     * @param in
     * @return
     * @throws IOException
     * @throws NoSuchAlgorithmException
     * @throws InvalidKeySpecException 
     */
    private static PublicKey receivePublicKey(final DataInputStream in) throws IOException, NoSuchAlgorithmException, InvalidKeySpecException{
        final byte[] keyBytes = new byte[in.readInt()];
        in.readFully(keyBytes);
        final KeyFactory kf = KeyFactory.getInstance(KeyExchange.DH.value());
        final X509EncodedKeySpec x509Spec = new X509EncodedKeySpec(keyBytes);
        final PublicKey theirPublicKey = kf.generatePublic(x509Spec);
        return theirPublicKey;
    }
    /**
     * Send our public key to client
     * @param out
     * @throws IOException 
     */
    private static void sendPublicKey(final DataOutputStream out) throws IOException{
        final byte[] keyBytes = keyPair.getPublic().getEncoded();
        out.writeInt(keyBytes.length);
        out.write(keyBytes);
    }
    /**
     * Calculate the secret value, using our private key and the client's public key.
     * The secret value is as long as the modulus used to generate the Diffie-Hellman keys (in this case, 1024)
     * @param theirPublicKey
     * @throws NoSuchAlgorithmException
     * @throws InvalidKeyException 
     */
    private static void generateSecretValue(final PublicKey theirPublicKey) throws NoSuchAlgorithmException, InvalidKeyException, UnsupportedEncodingException{
        final KeyAgreement ka = KeyAgreement.getInstance(KeyExchange.DH.value());
        ka.init(keyPair.getPrivate());
        ka.doPhase(theirPublicKey, true);
        secret = ka.generateSecret();
        //LOG.info(MessageFormat.format("Secret value generated by SkipServer:{0}", new String(Base64.encodeBase64(secret), "UTF-8")));
    }
    
    public static byte[] getSecret(){
        return secret;
    }
}
